你好,我是富士大顆 Aiko
想要少寫 JavaScript 嗎?(誤)
看這篇就對了!
會談到下列幾點:
Hotwire 是由 Basecamp 開發的前端框架,希望能讓開發者更有效率地製作網頁應用程式。核心理念是 "HTML over the wire" 也就是只用 HTML 來動態更新頁面,而不是大量的 JavaScript。所以會讓開發過程更簡單,也更能提高效能與維護性。
運作方式建立在將 HTML 內容從伺服器動態推送到客戶端的基礎上,達到實時更新和互動。Hotwire 主要由 Turbo 組成,時常會搭配 Stimulus。
當 user 想在不同頁面間移動,Turbo Drive 會攔截這些請求,然後只更新需要改變的頁面區塊,而不是重新 loading 整個頁面。
透過 AJAX 來讀取新頁面的 HTML,只替換變動的部分。
在一個頁面中部分更新 HTML。
可以用 turbo-frame 標籤來包一個 HTML 區塊。當該區塊需要更新時,只需要從伺服器傳送新的 turbo-frame 內容過來即可。
使用 WebSocket 或其他實時通訊協定來進行實時更新的機制。
伺服器可以發送特定的 Turbo Stream 指令(例如,新增、更新或刪除某個元素)到客戶端,客戶端會根據這些指令來更新頁面。(也就是新增、更新或刪除某個元素後的畫面各不相同)
雖然不是 Hotwire 的一部分,但 Stimulus 經常與 Turbo 一起使用。是一個輕量級的 JavaScript 框架,主要用於基本的前端邏輯,例如 response user 的動作(如點擊或鍵盤事件)。
Stimulus 使用標記和 JavaScript 代碼來控制客戶端行為。
兩者都是用於互動式的網頁應用程式,但著重的面向不同:
用於 Ruby on Rails 的現代前端框架,旨在簡化實時互動功能的開發。Stimulus Reflex 結合了 Stimulus.js(一個小型 JavaScript 框架)和 WebSocket(透過 ActionCable)來創造互動性的使用介面,無需編寫大量的客戶端 JavaScript 代碼。
相對 Stimulus 而言,Stimulus Reflex 通常用於更複雜的專案,Stimulus 提供基礎的客戶端互動,而 Stimulus Reflex 則是在這個基礎上加了由伺服器驅動的實時功能。兩者經常一起使用。
1. 事件觸發:user 進行了某種操作,比如點擊按鈕。
2. 伺服器反應:該事件會被發送到伺服器,伺服器執行相應的邏輯。
3. DOM 更新:伺服器會計算出需要更新哪些 DOM 元素,並將這些更新發送回客戶端。
4. 客戶端渲染:客戶端收到更新後,會實時更新 DOM。
假設有一個計數器:
class CounterReflex < ApplicationReflex
def increment
@count = element.dataset[:count].to_i + 1
end
end
CounterReflex 是一個 Reflex 類別,當 user 點擊一個按鈕來增加計數器時,會執行 increment 方法。dataset 通常用於儲存在 HTML 元素的自定資料屬性(data attributes),而這些屬性的值在 HTML 中總是字串所以要轉成數字存到 @count 變數,然後這個更新會透過 ActionCable (Rails 的 WebSocket 框架)自動廣播到客戶端。
new 一個新 Rails 專案時就安裝(Stimulus 也會一起安)
$rails new my_new_project --hot
或者在現有專案安裝 Hotwire
$bundle add hotwire-rails
建立 Model 們:
$rails g model Model'sname
建立資料庫,這是我的 schema:
ActiveRecord::Schema[7.0].define(version: 2023_10_14_093446) do
create_table "categories", force: :cascade do |t|
t.string "name", null: false
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_categories_on_user_id"
end
create_table "todos", force: :cascade do |t|
t.string "title", null: false
t.text "content"
t.integer "status", default: 0, null: false
t.datetime "due_date"
t.datetime "start_at"
t.datetime "end_at"
t.integer "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["category_id"], name: "index_todos_on_category_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "categories", "users"
add_foreign_key "todos", "categories"
end
建立 Controller(這是我的 categories controller):
class CategoriesController < ApplicationController
before_action :set_category, except: [:index, :new, :create]
def index
@categories = Category.all
end
def new
@category = Category.new
end
def create
@category = Category.new(params_category)
if @category.save
redirect_to categories_url, notice: 'Category was successfully created.'
else
render :new
end
end
def show
end
def edit
end
def update
if @category.update(params_category)
redirect_to @category, notice: 'Category was successfully updated.'
else
render :edit
end
end
def destroy
@category.destroy
redirect_to categories_url, notice: 'Category was successfully destroyed.'
end
private
def set_category
@category = Category.find_by(id: params[:id])
redirect_to categories_path, alert: "Category not found" unless @category
end
def params_category
params.require(:category).permit(:name)
end
end
設定 Routes:
Rails.application.routes.draw do
devise_for :users
root "categories#index"
resources :categories do
resources :todos
end
end
記得要建視圖!包括 index, show, new, edit(後面這兩個應該可以共用表單)
gem 'turbo-rails'
$bundle install
確認 application.js
有:
import "@hotwired/turbo-rails
通常不用另外設定,但某些情況會需要設定 data:{ turbo: false }